#include "stdafx.h"
#include "ObjData.h"

#include <StrSafe.h>

#define	DUMP_OBJ	0
/*	----------------------------------------------------------------------
	Global Structures/Declarations
	----------------------------------------------------------------------	*/
extern Options			theOptions;
/*	----------------------------------------------------------------------
	Local Structures/Declarations
	----------------------------------------------------------------------	*/

struct ThreadData
		{
		HWND		hwnd;
		SIZE_T		sequence;
		TCHAR		szMask[MAX_PATH+1];
		};
/*	----------------------------------------------------------------------
	Local Constants/Variables
	----------------------------------------------------------------------	*/
#ifdef	_DEBUG
	static const DWORD	kMsgTimeOut = 180000L;	// time out is 3 minutes
#else
	static const DWORD	kMsgTimeOut = 250L;		// time out is 1/4 second
#endif

static volatile long	s_tid = 0;
static ThreadData		s_threadData;

static const DWORD		kBufferSize = MB(8);
/*	----------------------------------------------------------------------
	Local Function Prototypes
	----------------------------------------------------------------------	*/
static bool			decompress(BYTE, BYTE const *, DWORD, XBuffer &);
static void			parseObjData(ObjInfo &, BYTE const *);
static unsigned WINAPI	threadMain(LPVOID);

inline static void
setFlagInfo(ObjInfo & obj, const FLAG_IMAGE_LIST nFlag, LPCTSTR pstr)
{
HRESULT	hr;
size_t	sLen = strlen(obj.szInfo);

if (GOOD_PTR(pstr) && sLen < kStringLen)
	{
	if (sLen > 0)
		{
		hr = StringCbCat(obj.szInfo, kStringLen, _T(" / "));
		ATLASSERT( SUCCEEDED(hr) );
		}
	
	hr = StringCbCat(obj.szInfo, kStringLen, pstr);
	ATLASSERT( SUCCEEDED(hr) );
	}

if (nFlag < obj.nFlag)
	obj.nFlag = nFlag;
}

inline static void		
updateCRC(DWORD & dwCRC, BYTE const * ptr, SIZE_T sdx)
	{
	while (sdx-- > 0)
		{
		dwCRC = (dwCRC & 0xFFFFFF00L) | ((dwCRC ^ *ptr++) & 0xFF);
		dwCRC = _rotl(dwCRC, 11);
		}
	}

/****************************************************************************
**
**	CloneObject() -- renames an object
**
*/
bool
CloneObject(
	ObjInfo const &	objInfo,
	CString const &	strName,
	const bool		bCustom)
{
XBuffer					buffer;
CAtlFileMapping<BYTE>	fileMap;
CAtlFile				fileNew,
						fileOld;
CString					strNameNew,
						strNameOld;

strNameOld = objInfo.szName;
strNameOld.TrimRight();

strNameNew = strName;
strNameNew.TrimRight();

try
	{
	//
	//	(1) Must be different from the old name
	//
	if (strNameOld == strNameNew)
		throw LPCTSTR( _T("The new name must be different from the current name.") );
	//
	//	(2) Must be valid
	//
	if (!IsValidName(strNameNew))
		throw LPCTSTR( _T("The new name is not valid.") );
	/*
	**	Read in the original data and decompress it.
	*/
	if (fileOld.Create(objInfo.szFile, GENERIC_READ, 0, OPEN_EXISTING) != S_OK ||
		fileMap.MapFile(fileOld) != S_OK)
		{
		throw LPCTSTR( _T("Could not read in existing .DAT file.") );
		}

	PBYTE			pRawData = reinterpret_cast<PBYTE>( fileMap.GetData() );
	SV6ObjHeaderPtr	pHeader = reinterpret_cast<SV6ObjHeaderPtr>( pRawData );
		
	if (!decompress(pHeader->xCompression, 
					MAKE_PTR(PBYTE, pRawData, sizeof(SV6ObjHeader)), 
					pHeader->dwChunkSize, buffer))
		{
		throw LPCTSTR( _T("Could not decompress existing .DAT file.") );
		}
	/*
	**	Change the name and recompute the CRC
	*/
	SV6ObjHeader	header = *pHeader;
	PBYTE			pType = PBYTE(&header);

	if (bCustom)
		*pType &= ~0xF0;	// clear the high nybble to mark as "custom"
		
	::FillMemory(header.xName, kSV6ObjNameLen, ' ');
	::CopyMemory(header.xName, LPCTSTR(strName), strName.GetLength());

	header.dwCRC = 0xF369A75B;
	updateCRC(header.dwCRC, pType, sizeof(BYTE));
	updateCRC(header.dwCRC, header.xName, sizeof(BYTE) * kSV6ObjNameLen);
	updateCRC(header.dwCRC, buffer.GetData(), buffer.GetCount());
	/*
	**	Write out the new file
	*/
	LPTSTR	pstr;
	TCHAR	szFile[MAX_PATH+1];
	
	strncpy(szFile, objInfo.szFile, MAX_PATH);
	pstr = strrchr(szFile, '\\');
	if (GOOD_PTR(pstr))
		pstr[1] = '\0';
	else
		szFile[0] = '\0';
		
	strncat(szFile, LPCTSTR(strNameNew), MAX_PATH);
	strncat(szFile, _T(".DAT"), MAX_PATH);
	if (fileNew.Create(szFile, GENERIC_ALL, 0, CREATE_NEW) != S_OK ||
		fileNew.Write(&header, sizeof(SV6ObjHeader)) != S_OK ||
		fileNew.Write(MAKE_PTR(PBYTE, pRawData, sizeof(SV6ObjHeader)), header.dwChunkSize) != S_OK)
		{
		throw LPCTSTR( _T("Could not create the new .DAT file.") );
		}
	
	fileMap.Unmap();
	fileOld.Close();
	fileNew.Close();
	}
catch (LPCTSTR pstrErr)
	{
	ATLASSERT( GOOD_PTR(pstrErr) );
	
	(void) AtlMessageBox(NULL, 
						pstrErr,
						_T("Copy Failed"),
						MB_OK|MB_ICONASTERISK);

	return false;
	}

return true;
}	/* end of CloneObject() */
/****************************************************************************
**
**	ExamineObject() 
**
*/
bool
ExamineObject(
	CAtlFile const &	file,
	ObjInfo &			objInfo)
{
CString					str;
int						nVal;
XBuffer					buffer;
CAtlFileMapping<BYTE>	fileMap;
bool					bIsObject = false;

objInfo.nFlag = FLAG_GREEN;

try
	{
	if (fileMap.MapFile(file) != S_OK)
		throw LPCTSTR( _T("File mapping error.") );
	//
	//	File size
	//
	PBYTE			pRawData = reinterpret_cast<PBYTE>( fileMap.GetData() );
	SV6ObjHeaderPtr	pHeader = reinterpret_cast<SV6ObjHeaderPtr>( pRawData );

	objInfo.sFileLen = fileMap.GetMappingSize();
	if (objInfo.sFileLen < SIZE_T(sizeof(SV6ObjHeader) + pHeader->dwChunkSize))
		throw LPCTSTR( _T("File size too small.") );
	
	if (objInfo.sFileLen > SIZE_T(sizeof(SV6ObjHeader) + pHeader->dwChunkSize))
		setFlagInfo(objInfo, FLAG_YELLOW, _T("File size too large (extra bytes ignored)") );
	//
	//	Object Author
	//
	nVal = *pRawData >> 4;	// only interested in high nybble
	
	switch (nVal)
		{
		case 0x00:	// custom!
			objInfo.author = OA_CUSTOM;
			break;
		case 0x01:	// wacky worlds?
			objInfo.author = OA_RCT2_WW;
			break;
		case 0x02:	// time twister
			objInfo.author = OA_RCT2_TT;
			break;
		case 0x08:	// original release
			objInfo.author = OA_RCT2;
			break;
		default:
			objInfo.author = OA_UNKNOWN;

			str.Format(_T("Unknown source: 0x%02X "), nVal);
			setFlagInfo(objInfo, FLAG_YELLOW, LPCTSTR(str) );
			break;
		}
		
	if (theOptions.bCustomOnly && IS_RCT2(objInfo.author))
		return false;	// not interested...	
	//
	//	Object type
	//
	nVal = *pRawData & 0x0F;
	if (IS_VALID_OBJTYPE(nVal))
		objInfo.type = ObjType(nVal);
	else
		{
		ATLTRACE("ExamineObject() => unknown type (0x%02X)\n", nVal);
		throw LPCTSTR( _T("Invalid object type.") );
		}
	//
	//	Decompress the data and remember the uncompressed size.  Use a 2:1
	//	"guesstimate" for memory
	//
	if (!decompress(pHeader->xCompression, 
					MAKE_PTR(PBYTE, pRawData, sizeof(SV6ObjHeader)), 
					pHeader->dwChunkSize, buffer))
		{
		throw LPCTSTR( _T("Decompression failed") );
		}

	bIsObject = true;	// OK if decompression worked and size and type are valid
	objInfo.sDataLen = buffer.GetCount();

	parseObjData(objInfo, buffer.GetData());
	//
	//	Object name -- be careful, because the name is NOT zero-terminated!
	//
	TCHAR	szFile[_MAX_FNAME+1];
	
	_splitpath(objInfo.szFile, NULL, NULL, szFile, NULL);
	::CopyMemory(objInfo.szName, pHeader->xName, kSV6ObjNameLen);

	if (!IsValidName(objInfo.szName))
		setFlagInfo(objInfo, FLAG_YELLOW, _T("Invalid name!") );
		
#if 0	// This convention was dropped after WW?
	if (StrCmpPartial(objInfo.szName, szFile) != CSTR_EQUAL)
		setFlagInfo(objInfo, FLAG_YELLOW, _T("Filename and object name do not match!") );
#endif 
	//
	//	Generate an MD5 hash to help detect duplicates
	//
	MD5		md5;

	md5.Start();	
	md5.Update(buffer.GetData(), objInfo.sDataLen);
	md5.Stop();
	md5.GetDigest(objInfo.digest);	
	//
	//	Check the CRC
	//
	objInfo.dwCRC = 0xF369A75B;
	updateCRC(objInfo.dwCRC, pRawData, sizeof(BYTE));
	updateCRC(objInfo.dwCRC, pHeader->xName, sizeof(BYTE) * kSV6ObjNameLen);
	updateCRC(objInfo.dwCRC, buffer.GetData(), objInfo.sDataLen);
		
	if (objInfo.dwCRC != pHeader->dwCRC)
		throw LPCTSTR( _T("Invalid CRC") );
	//
	//	Compare to objData
	//
	if (theOptions.bVerify && IS_RCT2(objInfo.author))
		{
		int				nCmp;
		bool			bOK = true;
		ObjData const *	pOD = kObjData;
		
		for (int ndx = 0; ndx < ARRAY_DIM(kObjData); ++ndx, ++pOD)
			{
			ATLASSERT( !::IsBadReadPtr(pOD, sizeof(ObjData)) );
			
			nCmp = StrCmpPartial(pOD->pstrName, objInfo.szName);
			if (nCmp == CSTR_EQUAL && pOD->nAuthor == objInfo.author)
				{
				str = _T("Verification failed:");
				if (pOD->sDataLen != objInfo.sDataLen)
					{
					bOK = false;
					str += _T(" size");
					}
				
				if (pOD->dwCRC != objInfo.dwCRC)
					{
					bOK = false;
					str += _T(" CRC");
					}
				
				if (StrCmpPartial(pOD->pstrMD5, MD5::ConvertToASCII(objInfo.digest)) != CSTR_EQUAL)
					{
					bOK = false;
					str += _T(" MD5");
					}
				
				if (!bOK)
					{
					ATLASSERT( !str.IsEmpty() );
					setFlagInfo(objInfo, FLAG_YELLOW, LPCTSTR(str) );
					}
				break;
				}
			else if (nCmp == CSTR_GREATER_THAN)
				break;
			}
		}
	}
catch (LPCTSTR pstrWhy)
	{
	setFlagInfo(objInfo, FLAG_RED, pstrWhy );
	}
	
return bIsObject;
}	/* end of ExamineObject() */
/****************************************************************************
**
**	ScanObjectFolder() 
**
*/
void
ScanObjectFolder(
	SIZE_T		sequence,
	HWND		hwnd,
	LPCTSTR		pstrMask)
{
ATLASSERT( GOOD_HND(hwnd) );
ATLASSERT( !::IsBadStringPtr(pstrMask, MAX_PATH) );

if (s_tid == 0)
	{
	unsigned			uDummy;

	::ZeroMemory(&s_threadData, sizeof(ThreadData));
	s_threadData.hwnd		= hwnd;
	s_threadData.sequence	= sequence;
	strncpy(s_threadData.szMask, pstrMask, MAX_PATH);

	(void) _beginthreadex(NULL, 0, threadMain, NULL, 0, &uDummy);
	}
else
	::MessageBeep(MB_ICONASTERISK);
}	/* end of ScanObjectFolder() */
/****************************************************************************
**
**	IsValidName() -- validates an object name
**
*/
bool
IsValidName(
	LPCTSTR	pstrName)
{
CString		strName = pstrName;
//
//	(1) Isn't blank
//
strName.TrimRight();
if (strName.IsEmpty() || strName.GetLength() > kSV6ObjNameLen)
	return false;
//
//	(2) Cannot contain leading spaces
//
if (strName.GetAt(0) <= ' ')
	return false;
//
//	(3) Cannot contain invalid characters.  Check for non-standard characters
//		BEFORE calling isalnum() to avoid an ASSERT inside _ctype.c
//
for (int ndx = 0; ndx < strName.GetLength(); ++ndx)
	{
	TCHAR	ch = strName.GetAt(ndx);
	
	if (ch < ' ' || strchr(_T("\\/:*?\"<>"), ch) != NULL)
		{
		ATLTRACE("Found invalid character '%c' in name.\n", ch);
		return false;
		}
	}
	
return true;
}	/* end of IsValidName() */
/****************************************************************************
**
**	LoadObjectImageData() -- creates a bitmap based on an object
**
*/
int								// count of images
LoadObjectImageData(
	ObjInfo const &	objInfo,	// object information
	XBuffer &		buffer)		// [out] image data
{
buffer.RemoveAll();
if (!objInfo.sImageDir || objInfo.nImages <= 0)
	return 0;
//
//	Parse through the object data to get to the start of the graphics data
//
CWaitCursor				waiter;
CAtlFile				file;
CAtlFileMapping<BYTE>	fileMap;
CString					strErrMsg;

try	
	{
	//
	//	open and map the .DAT file
	//
	SV6ObjHeaderPtr	pHeader;
	PBYTE			pRawData;
	
	if (file.Create(objInfo.szFile, GENERIC_READ, 0, OPEN_EXISTING) != S_OK ||
		fileMap.MapFile(file) != S_OK)
		{
		strErrMsg = _T("Could not read in existing .DAT file.");
		throw DWORD( ::GetLastError() );
		}

	pRawData = reinterpret_cast<PBYTE>( fileMap.GetData() );
	pHeader = reinterpret_cast<SV6ObjHeaderPtr>( pRawData );
	//
	//	decompress the data
	//
	if (!buffer.SetCount( objInfo.sDataLen ))
		{
		strErrMsg = _T("Could not allocate memory for image data.");
		throw DWORD( ERROR_NOT_ENOUGH_MEMORY );
		}

	if (!decompress(pHeader->xCompression, 
					MAKE_PTR(PBYTE, pRawData, sizeof(SV6ObjHeader)), 
					pHeader->dwChunkSize,
					buffer))
		{
		strErrMsg = _T("Could not decompress data.");
		throw DWORD( ERROR_INVALID_DATA );
		}

	ATLASSERT(buffer.GetCount() > objInfo.sImageDir);
	
	if ((objInfo.sImageDir + sizeof(SV6ObjImageDir)) > buffer.GetCount())
		{
		strErrMsg = _T("Could not locate internal image directory.");
		throw DWORD( ERROR_INVALID_DATA );
		}

	ATLTRACE("Discarding %ld bytes of non-image data.\n", objInfo.sImageDir);
	buffer.RemoveAt(0, objInfo.sImageDir);
	}
catch (DWORD dwError)
	{
	buffer.RemoveAll();
	if (strErrMsg.IsEmpty())
		strErrMsg.Format(_T("Could not display image: error 0x%08lX."), dwError);

	(void) AtlMessageBox(NULL, LPCTSTR(strErrMsg), kStrAppTitle, MB_OK|MB_ICONSTOP);
	}
	
return (buffer.GetCount() > 0) ? objInfo.nImages : 0;
}	/* end of LoadObjectImageData() */
/****************************************************************************
**
**	threadMain() 
**
*/
static unsigned WINAPI
threadMain(
	LPVOID)
{
COPYDATASTRUCT	cds;
CFindFile		finder;
DWORD			dwIgnored;
ThreadData		tData = s_threadData;

ATLASSERT( GOOD_HND(tData.hwnd) );
ATLASSERT( strlen(tData.szMask) > 0 );

::InterlockedExchange(&s_tid, _threadid);
::ZeroMemory(&cds, sizeof(COPYDATASTRUCT));

if (finder.FindFile(tData.szMask))
	{
	CAtlFile	file;
	ObjInfo		objInfo;

#if	DUMP_OBJ
	bool		bDump;
	CAtlFile	fileDump;
	
	bDump = (fileDump.Create(_T("ObjDump.txt"), GENERIC_ALL, 0, CREATE_NEW) == S_OK);
#endif

	do
		{
		::ZeroMemory(&objInfo, sizeof(ObjInfo));
		if (finder.GetFilePath(objInfo.szFile, MAX_PATH) &&
			file.Create(objInfo.szFile, GENERIC_READ, 0, OPEN_EXISTING) == S_OK)
			{
			if (ExamineObject(file, objInfo))
				{
#if	DUMP_OBJ
				if (bDump && IS_RCT2(objInfo.author))
					{
					TCHAR	szOut[512];
					int		nLen = _snprintf(	szOut, 512,
												_T("{ \"%s\", 0x%02X, %lu, 0x%08lX,\"%s\" },\n"),
												objInfo.szName, objInfo.author,
												objInfo.sDataLen, objInfo.dwCRC,
												MD5::ConvertToASCII(objInfo.digest));

					fileDump.Write(szOut, nLen);
					}
#endif
				cds.cbData	= sizeof(ObjInfo);
				cds.lpData	= &objInfo;
				cds.dwData	= tData.sequence;
				if (!::SendMessageTimeout(tData.hwnd, WM_COPYDATA, 
											NULL, LPARAM(&cds), 
											SMTO_ABORTIFHUNG|SMTO_NORMAL, 
											kMsgTimeOut, &dwIgnored))
					{
					::InterlockedExchange(&s_tid, 0);
					_endthreadex( ::GetLastError() );
					return -1;	// should never get here!
					}
				}

			file.Close();
			}
		}
	while (finder.FindNextFile());
	
#if	DUMP_OBJ
	if (bDump)
		fileDump.Close();
#endif
	}
//
//	Send a final WMU_END_SCAN message to signal that we're done now.
//
(void) ::SendMessageTimeout(tData.hwnd, WMU_END_SCAN, 
							NULL, LPARAM(tData.sequence), 
							SMTO_ABORTIFHUNG|SMTO_NORMAL, 
							kMsgTimeOut, &dwIgnored);

::InterlockedExchange(&s_tid, 0);
_endthreadex(0);

return 0;	// should never get here!
}	/* end of threadMain() */
/****************************************************************************
**
**	decompress() -- decompresses a chunk of RCT data into s_buffer
**
*/
static bool						// true on success, false on failure
decompress(
	BYTE			xType,		// decompression type (0x00...0x03)
	BYTE const *	pData,		// ptr to compressed data
	DWORD			dwDataLen,	// length of compressed data in bytes
	XBuffer &		buffer)		// [out] uncompressed data
{
static BYTE		s_buffer[kBufferSize];

DWORD			dwSize = 0;

if (dwDataLen == 0)
	return false;

switch (xType)
	{
	case 0x00:	// no compression
		dwSize = dwDataLen;
		if (!buffer.SetCount(dwSize))
			return false;
			
		::CopyMemory(buffer.GetData(), pData, dwDataLen);
		break;

	case 0x01:	// RLE compression
		{
		BYTE	xVal;
		int		nCount;
		DWORD	dwNext = 0;

		do
			{
			xVal = pData[dwNext++];
			//
			//	Test the MSB.  If it is 1, then the next byte is copied (1 - xVal)
			//	times.  If it is 0, then the next xVal bytes are copied as is.
			//
			if (xVal & 0x80)
				{
				nCount = 1 - char(xVal);
				if (nCount <= 0 || nCount >= 0x80 || 
					dwNext >= dwDataLen ||
					(dwSize + nCount) >= kBufferSize)
					return false;
					
				xVal = pData[dwNext++];
				::FillMemory(&s_buffer[dwSize], UINT(nCount), xVal);
				}
			else
				{
				nCount = 1 + char(xVal);
				if ((dwSize + nCount) >= kBufferSize)
					return false;
					
				::CopyMemory(&s_buffer[dwSize], &pData[dwNext], UINT(nCount));
				dwNext += nCount;
				}

			dwSize += nCount;
			}
		while (dwNext < dwDataLen);
		
		if (!buffer.SetCount(dwSize))
			return false;
		::CopyMemory(buffer.GetData(), s_buffer, dwSize);
		}
		break;

	case 0x02:
		{
		BYTE		xVal;
		DWORD		dwNext = 0;
		
		if (!decompress(0x01, pData, dwDataLen, buffer))	// do the normal RLE decompression
			return false;
			
		ATLASSERT(buffer.GetCount() > 0);
		pData = buffer.GetData();
		
		do
			{
			xVal = pData[dwNext++];
			if (xVal == 0xFF)
				{
				if (dwNext >= dwDataLen || dwSize >= kBufferSize)
					return false;
					
				s_buffer[dwSize++] = pData[dwNext++];	//	Copy the next byte directly into the output data stream
				}
			else
				{
				DWORD	dwLen = (xVal & 0x07) + 1;
				int		nOffset = int((xVal & 0xF8) >> 3) - 32L;

				if ((dwSize + nOffset) >= dwSize ||		// not enough src data
					(dwSize + dwLen) > kBufferSize)		// not enough dst room
					{
					return false;	
					}
				
				::CopyMemory(&s_buffer[dwSize], &s_buffer[dwSize + nOffset], dwLen);
				dwSize += dwLen;
				}
			}
		while (dwNext < dwDataLen);

		if (!buffer.SetCount(dwSize))
			return false;
		::CopyMemory(buffer.GetData(), s_buffer, dwSize);
		}
		break;

	case 0x03:
		{
		int		nRotateBy = 1;
		BYTE *	pBuffer = s_buffer;

		if (dwDataLen >= kBufferSize)
			return false;
			
		dwSize = dwDataLen;
		::CopyMemory(pBuffer, pData, dwSize);

		for (DWORD dwx = 0; dwx < dwSize; ++dwx, ++pBuffer)
			{
			if (*pBuffer)	// don't bother to rotate if zero
				*pBuffer = ROTR8(*pBuffer, nRotateBy);

			if ((nRotateBy += 2)  > 7)
				nRotateBy = 1;
			}

		if (!buffer.SetCount(dwSize))
			return false;
		::CopyMemory(buffer.GetData(), s_buffer, dwSize);
		}
		break;

	default:
		return false;
	}

ATLASSERT( dwSize > 0 );
ATLASSERT( buffer.GetCount() == dwSize );

return true;
}	/* end of decompress() */
/****************************************************************************
**
**	parseObjData() -- reads the string table information from the object 
**		data
**
*/
static void
parseObjData(
	ObjInfo &		objInfo,
	BYTE const *	pData)
{
SIZE_T	sOffset = 0;
int		nStrings = 1;	// most objects have only a single string table
bool	bGroupInfo = true,
		bHasGraphics = true;

switch (objInfo.type)
	{
	case OT_RIDE_SHOP:
		sOffset = 0x1C2;
		nStrings = 3;
		bGroupInfo = false;
		break;
	case OT_SM_SCENERY:
		sOffset = 0x1C;
		break;
	case OT_LG_SCENERY:
		sOffset = 0x1A;
		break;
	case OT_WALLS:
		sOffset = 0x0E;
		break;
	case OT_BANNERS:
		sOffset = 0x0C;
		break;
	case OT_PATHS:
		sOffset = 0x0E;
		bGroupInfo = false;
		break;
	case OT_PATH_ITEMS:
		sOffset = 0x0E;
		break;
	case OT_GROUP:
		sOffset = 0x10E;
		bGroupInfo = false;
		break;
	case OT_ENTRANCE:
		sOffset = 0x08;
		bGroupInfo = false;
		break;
	case OT_WATER:
		sOffset = 0x10;
		bGroupInfo = false;
		break;
	case OT_SC6_TEXT:
		sOffset = 0x08;
		nStrings = 3;
		bGroupInfo = false;
		bHasGraphics = false;
		break;

	case OT_UNKNOWN:
	default:
		// do nothing
		break;
	}

if (sOffset == 0 || NULL_PTR(pData))
	return;

ATLASSERT( sOffset < objInfo.sDataLen );
//
//	Now start parsing the string table
//	
HRESULT			hr;
CString			str;
BYTE const *	pc = &pData[sOffset]; 

for (int ndx = 0; ndx < nStrings; ++ndx, ++pc)
	{
	while (*pc != 0xFF)
		{
		if (*pc == theOptions.nLanguage)
			{
			if (str.IsEmpty())
				str = LPCTSTR(&pc[1]);
			else
				{
				str += _T("\r\n");
				str += LPCTSTR( &pc[1] );
				}
			
			str.TrimRight();
			}

		pc += 1 + strlen( LPCTSTR(&pc[1]) ) + 1;
		}
	}

if (!str.IsEmpty())
	{
	hr = StringCbCopy(objInfo.szStrings, kStringLen, LPCTSTR(str));
	ATLASSERT( SUCCEEDED(hr) );
	}

sOffset += SIZE_T(pc - &pData[sOffset]);	// => past end of last string
//
//	Process the Group Info
//
if (bGroupInfo)
	{
#ifdef	_DEBUG
	SV6ObjGroup *	pGrp = MAKE_PTR(SV6ObjGroup *, pData, sOffset);
#endif
	sOffset += sizeof(SV6ObjGroup);	// => past end of Group Info
	}
//
//	Process the "optional" data, which is unique for each object type
//
switch (objInfo.type)
	{
	case OT_RIDE_SHOP:
		{
		BYTE	xVal = pData[sOffset++];

		sOffset += 3 * ((xVal == 0xFF) ? 32 : SIZE_T(xVal));
		
		for (int ndx = 0; ndx < 4; ++ndx)
			{
			if ((xVal = pData[sOffset++]) != 0xFF)
				sOffset += xVal;
			else
				{
				WORD const *	pwLen = MAKE_PTR(WORD const *, pData, sOffset);
				
				ATLASSERT( GOOD_PTR(pwLen) );
				sOffset += sizeof(WORD) + *pwLen;
				}
			}
		}
		break;
		
	case OT_SM_SCENERY:
		{
		DWORD const *	pdwFlags = MAKE_PTR(DWORD const *, pData, 0x06);

		ATLASSERT( GOOD_PTR(pdwFlags) );
		if (*pdwFlags & 0x00000010)	// true if animation data exists
			{
			while (pData[sOffset++] != 0xFF)
				{ /* EMPTY LOOP */ }
			}

		ATLASSERT( sOffset < objInfo.sDataLen );
		}
		break;

	case OT_LG_SCENERY:
		{
		BYTE const *	pFlags = MAKE_PTR(BYTE const *, pData, 0x07);

		ATLASSERT( GOOD_PTR(pFlags) );
		if (*pFlags & 0x04)	// true if 3D text
			sOffset += 0x040E;	// skip over 3D text information
			
		for (;;)
			{
			WORD const *	pwTile = MAKE_PTR(WORD const *, pData, sOffset);

			if (*pwTile != 0xFFFF)
				sOffset += 9;
			else
				{
				sOffset += sizeof(WORD);
				break;
				}
			}

		ATLASSERT( sOffset < objInfo.sDataLen );
		}
		break;

	case OT_GROUP:
		while (pData[sOffset] != 0xFF)
			{
#ifdef	_DEBUG
			SV6ObjGroup *	pGrp = MAKE_PTR(SV6ObjGroup *, pData, sOffset);
#endif
			sOffset += sizeof(SV6ObjGroup);	// => past end of Group Info
			}

		sOffset++;	// skip over 0xFF flag

		ATLASSERT( sOffset < objInfo.sDataLen );
		break;

	default:
		// do nothing
		break;
	}
//
//	Now save the start of the graphics data
//
SV6ObjImageDir *	pID = MAKE_PTR(SV6ObjImageDir *, pData, sOffset);

if (bHasGraphics &&
	GOOD_PTR(pID) && 
	pID->dwImageCount > 0 && pID->dwImageCount < SHRT_MAX)
	{
	objInfo.nImages		= int(pID->dwImageCount);
	objInfo.sImageDir	= sOffset;
	
	ATLASSERT( objInfo.sImageDir < objInfo.sDataLen );
	}
}	/* end of parseObjData() */
/****************************************************************************
**
**	$History: SV6Object.cpp $
 * 
 * *****************  Version 10  *****************
 * User: Neusel       Date: 2/27/04    Time: 8:56a
 * Updated in $/DatChecker.root/DatChecker
 * v1.9 -- Added drag and drop handler.
 * 
 * *****************  Version 9  *****************
 * User: Neusel       Date: 1/21/04    Time: 6:40p
 * Updated in $/DatChecker.root/DatChecker
 * 
 * *****************  Version 8  *****************
 * User: Neusel       Date: 1/20/04    Time: 12:08p
 * Updated in $/DatChecker.root/DatChecker
 * v1.7 -- rearranged Properties dialog and added yellow flags.
 * 
 * *****************  Version 7  *****************
 * User: Neusel       Date: 1/02/04    Time: 12:37p
 * Updated in $/DatChecker.root/DatChecker
 * Modified decompress() to use dynamically allocated arrays.
 * CObjViewDlg now caches image data.
 * 
 * *****************  Version 6  *****************
 * User: Neusel       Date: 12/31/03   Time: 4:48p
 * Updated in $/DatChecker.root/DatChecker
 * v1.5 -- Added object image viewing.
 * 
 * *****************  Version 5  *****************
 * User: Neusel       Date: 12/17/03   Time: 8:58a
 * Updated in $/DatChecker.root/DatChecker
 * 
 * *****************  Version 4  *****************
 * User: Neusel       Date: 10/27/03   Time: 11:43a
 * Updated in $/DatChecker.root/DatChecker
 * v1.3 -- added "/verify" flag to make object data check optional.
 * 
 * *****************  Version 3  *****************
 * User: Neusel       Date: 9/08/03    Time: 9:34a
 * Updated in $/DatChecker.root/DatChecker
 * v1.2 -- added ObjData header file and internal checks.
 * 
 * *****************  Version 2  *****************
 * User: Neusel       Date: 8/14/03    Time: 3:59p
 * Updated in $/DatChecker.root/DatChecker
 * Added "Object Clone" and String Table capability.
 * 
 * *****************  Version 1  *****************
 * User: Neusel       Date: 8/14/03    Time: 9:25a
 * Created in $/DatChecker.root/DatChecker
**
**	-------------------------------------------------------------------------
**
**	End of SV6Object.cpp
**
****************************************************************************/
